﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Bymed.SpineScan.DataModel;
using Bymed.SpineScan.Repository.Interface;
using Bymed.SpineScan.Service.Interface;

namespace Bymed.SpineScan.Service.Shared
{
    public static class CommonService
    {
        /// <summary>
        /// Delete specified TestTracking with all related data, includes TestResult and TestResultDiagnosticMessage.
        /// </summary>
        /// <param name="repositoryFactory">Instance of a class implements IRepositoryFactory inteface.</param>
        /// <param name="testTracking">The string value of TestTimestamp.</param>
        public static void DeleteTestTracking(IRepositoryFactory repositoryFactory, string testTimestamp)
        {
            var testTrackingRepository = repositoryFactory.CreateTestTrackingRepository();
            var testTracking = testTrackingRepository.Query(t => t.TestTimestamp == testTimestamp, 
                "TestResults;TestResultDiagnosticMessages").FirstOrDefault();

            DeleteTestTracking(repositoryFactory, testTracking);
        }

        /// <summary>
        /// Delete specified TestTracking with all related data, includes TestResult and TestResultDiagnosticMessage.
        /// </summary>
        /// <param name="repositoryFactory">Instance of a class implements IRepositoryFactory inteface.</param>
        /// <param name="testTracking">The specified instance of TestTracking that will be deleted. It should 
        /// contain TestResults & TestResultDiagnosticMessages data.</param>
        public static void DeleteTestTracking(IRepositoryFactory repositoryFactory, TestTracking testTracking)
        {
            if (testTracking != null)
            {
                var testTrackingRepository = repositoryFactory.CreateTestTrackingRepository();
                var testResultRepository = repositoryFactory.CreateTestResultRepository();
                var testResultDiagnosticMessageRepository = repositoryFactory.CreateTestResultDiagnosticMessageRepository();

                // Delete all related TestResults.
                while (testTracking.TestResults.Count > 0)
                {
                    testResultRepository.Delete(testTracking.TestResults.First());
                }

                // Delete all related TestResultDiagnosticMessages.
                while (testTracking.TestResultDiagnosticMessages.Count > 0)
                {
                    testResultDiagnosticMessageRepository.Delete(testTracking.TestResultDiagnosticMessages.First());
                }

                // Delete current TestTracking.
                testTrackingRepository.Delete(testTracking);
            }
        }

        /// <summary>
        /// Add the provided entity if the primary key value is not existing. 
        /// Otherwise the entity will be added as new data with new primary key value even the data might be duplicated.
        /// </summary>
        /// <param name="repository">Repository instance implements IRepository.</param>
        /// <param name="entities">The data entity list.</param>
        /// <param name="primaryKey">The name of primary key.</param>
        /// <param name="entityValidator">The data validator.</param>
        /// <param name="changedPrimaryKeyMapping">Primary key mapping. Use the old primary key as key, new primary key as value.</param>
        /// <returns>The list of data entity list. Each entity might contains the new primary key value.</returns>
        public static IList<T> AddOrDuplicate<T>(IRepository<T> repository, IList<T> entities, string primaryKey,
            IDataValidator<T> entityValidator, Dictionary<string, string> changedPrimaryKeyMapping) where T : class
        {
            foreach (var entity in entities)
            {
                AddOrDuplicate<T>(repository, entity, primaryKey, entityValidator, changedPrimaryKeyMapping);
            }

            return entities;
        }

        /// <summary>
        /// Add the provided entity if the primary key value is not existing. 
        /// Otherwise the entity will be added as new data with new primary key value even the data might be duplicated.
        /// </summary>
        /// <param name="repository">Repository instance implements IRepository.</param>
        /// <param name="entity">The data entity.</param>
        /// <param name="primaryKey">The name of primary key.</param>
        /// <param name="entityValidator">The data validator.</param>
        /// <param name="changedPrimaryKeyMapping">Primary key mapping. Use the old primary key as key, new primary key as value.</param>
        /// <returns>The data entity might contains the new primary key value.</returns>
        public static T AddOrDuplicate<T>(IRepository<T> repository, T entity, string primaryKey, 
            IDataValidator<T> entityValidator, Dictionary<string, string> changedPrimaryKeyMapping) where T : class
        {
            var currentPrimaryKeyValue = GeneralUtility.GetPropertyValue(entity, primaryKey);
            var existingDataCount = repository.GetRowCount(GeneralUtility.IsPropertyValueMatchedInputValue<T>(primaryKey, currentPrimaryKeyValue));

            if (existingDataCount > 0)
            {
                // If the input primary key value is existing, system will generate a new one.
                var newPrimarykey = GenerateNewPrimaryKey<T>(repository, entity, primaryKey, (string)GeneralUtility.GetPropertyValue(entity, primaryKey), 1);

                // Save the mapping. Then client can get the updated primary key value by accessing this Dictionary using original primary key value.
                changedPrimaryKeyMapping.Add((string)GeneralUtility.GetPropertyValue(entity, primaryKey), newPrimarykey);

                // Set the new primary key value to this property.
                GeneralUtility.SetPropertyValue(entity, primaryKey, newPrimarykey);
            }
            else
            {
                // Save the mapping. Then client can get the unchanged primary key value by accessing this Dictionary using original primary key value.
                changedPrimaryKeyMapping.Add((string)GeneralUtility.GetPropertyValue(entity, primaryKey), 
                    (string)GeneralUtility.GetPropertyValue(entity, primaryKey));
            }

            // Validate the entity.
            entityValidator.Validate(entity);

            if (entityValidator.IsValid)
            {
                entity = repository.Add(entity);
            }
            else
            {
                entity = null;
            }

            return entity;
        }

        /// <summary>
        /// Generate new primary key based on provided existing key. For example: existing key is RBK20130318001, the new one 
        /// will be RBK201303180011, RBK201303180012 or RBK201303180013, etc.
        /// </summary>
        /// <param name="repository">Repository instance implements IRepository.</param>
        /// <param name="entity">The data entity.</param>
        /// <param name="primaryKey">The primary key name.</param>
        /// <param name="existingPrimaryKeyValue">The existing primary key.</param>
        /// <param name="extendedIndex">The extended index used to generate not duplicated key.</param>
        /// <returns>New generated not duplicated key.</returns>
        private static string GenerateNewPrimaryKey<T>(IRepository<T> repository, T entity, 
            string primaryKey, string existingPrimaryKeyValue, int extendedIndex) where T : class
        {
            // Generate the new value.
            var newPrimaryKeyValue = string.Format("{0}{1}", existingPrimaryKeyValue, extendedIndex);

            // Check if this new value is existing or not.
            if (repository.GetRowCount(GeneralUtility.IsPropertyValueMatchedInputValue<T>(primaryKey, newPrimaryKeyValue)) == 0)
            {
                return newPrimaryKeyValue;
            }
            else
            {
                // Change the extendedIndex and generate the new value again.
                extendedIndex += 1;

                return GenerateNewPrimaryKey<T>(repository, entity, primaryKey, existingPrimaryKeyValue, extendedIndex);
            }
        }
    }
}
